#include <string.h>
#include <stdio.h>
#ifdef __linux
int fileno ( FILE* ); /* in stdio.h? */
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <io_uni.h>

void IO_set_header_for_write ( IO_filehand* fh, IO_uni_header* header,
			       int size_of_element,
			       int elements_per_site, int* dim, int order )
{
  header -> magic_number = IO_UNI_MAGIC;
  header -> gmtime = fh -> otime;
  header -> size_of_element = size_of_element;
  header -> elements_per_site = elements_per_site;
  header -> dim[0] = dim[0];
  header -> dim[1] = dim[1];
  header -> dim[2] = dim[2];
  header -> dim[3] = dim[3];
  header -> site_order = order;
}

#define SBUF_SIZE 64
int IO_write_header ( IO_filehand* fh, IO_uni_header* header )
{
  int indx, ret, dir, status = 0;
  char kbuf[SBUF_SIZE], vbuf[SBUF_SIZE];
  IO_meta_item entry;
  time_t gtime;

  /* write binary header */
  if ( fh -> swap_bytes )
    {
      int cnt;
      IO_type32* buf = static_cast ( IO_type32*,
				     malloc (sizeof(IO_uni_header)));
      memcpy ( buf, header, sizeof(IO_uni_header) );

      for ( cnt = 0; cnt < sizeof(IO_uni_header)/sizeof(IO_type32); ++cnt )
	IO_swap32 ( buf + cnt );

      ret = fwrite ( static_cast ( void*, buf ), sizeof(char),
		     sizeof(IO_uni_header), fh -> data_fp );
      if ( ret != sizeof(IO_uni_header) )
	status += 1;

      free ( buf );
    }
  else
    {
      ret = fwrite ( static_cast ( void*, header ), sizeof(char),
		     sizeof(IO_uni_header), fh -> data_fp );
      if ( ret != sizeof(IO_uni_header) )
	status += 1;
    }

  /* write header meta-data */
  entry.value = vbuf;

  entry.key   = "header.timestamp";
  gtime = header -> gmtime;
  strncpy ( entry.value, asctime ( gmtime ( &gtime ) ),
				  SBUF_SIZE );
  for ( indx = strlen ( entry.value ) - 1; indx > 0; --indx )
    if ( entry.value[indx] == '\n' )
      {
	entry.value[indx] = 0;
	break;
      }
  strncat ( entry.value, " UTC", SBUF_SIZE );
  IO_write_meta_item ( fh, &entry );

  entry.key   = "header.size-of-element";
  sprintf ( entry.value, "%d", header -> size_of_element );
  IO_write_meta_item ( fh, &entry );

  entry.key   = "header.elements-per-site";
  sprintf ( entry.value, "%d", header -> elements_per_site );
  IO_write_meta_item ( fh, &entry );

  entry.key   = kbuf;
  for ( dir = 0; dir < 4; ++dir )
    {
      sprintf ( entry.key, "header.dim[%d]", dir );
      sprintf ( entry.value, "%d", header -> dim[dir] );
      IO_write_meta_item ( fh, &entry );
    }

  entry.key   = "header.site-order";
  sprintf ( entry.value, "%d", header -> site_order );
  IO_write_meta_item ( fh, &entry );

  return status;
}

int IO_write_site_data ( IO_filehand* fh, IO_uni_header* header,
			 int nsites, void* site_data )
{
  int ret, status = 0;

  if ( fh -> swap_bytes )
    {
      /* TODO swapping */
      fprintf ( stderr, "don't know how to swap bytes on write!\n" );
    }

  ret = fwrite ( site_data, header -> size_of_element,
		 nsites * header -> elements_per_site, fh -> data_fp );
  if ( ret != nsites *
       header -> size_of_element * header -> elements_per_site )
    status += 1;

  return status;
}

int IO_read_header ( IO_filehand* fh, IO_uni_header* header )
{
  int ret, status = 0;
  int items = sizeof ( IO_uni_header ) / sizeof ( IO_type32 );

  ret = fread ( static_cast ( void*, header ), sizeof ( IO_type32 ),
		items, fh -> data_fp );
  if ( ret != items )
    status += 1;

  if ( header -> magic_number != IO_UNI_MAGIC )
    {
      /* try byte swapping */
      IO_uni_header buf = *header;
      IO_type32 *p = static_cast ( IO_type32*, &buf );
      int cnt;
      for ( cnt = 0; cnt < items; ++cnt )
	IO_swap32 ( p + cnt );

      if ( buf.magic_number != IO_UNI_MAGIC )
	{
	  fprintf ( stderr, "WARNING: unknown header read.\n" );
	  status += 2;
	}
      else
	{
	  /* this file needs byte swapping */
	  fh -> swap_bytes = 1;
	  *header = buf;
	}
    }

  return status;
}

void IO_print_header ( FILE *out, IO_uni_header* header )
{
  time_t gtime;

  fprintf ( out, "header.magic-number 0x%8x\n", header -> magic_number );
  gtime = header -> gmtime;
  fprintf ( out, "header.timestamp (UTC) %s",
	    asctime ( gmtime ( &gtime ) ) );
  fprintf ( out, "header.size-of-element %d\n", header -> size_of_element );
  fprintf ( out, "header.elements-per-site %d\n",
	    header -> elements_per_site );
  fprintf ( out, "header.dim[0] %d\n", header -> dim[0] );
  fprintf ( out, "header.dim[1] %d\n", header -> dim[1] );
  fprintf ( out, "header.dim[2] %d\n", header -> dim[2] );
  fprintf ( out, "header.dim[3] %d\n", header -> dim[3] );
  fprintf ( out, "header.site-order %d\n", header -> site_order );
}

int IO_read_site_data ( IO_filehand* fh, IO_uni_header* header,
			 int nsites, void* site_data )
{
  int ret, status = 0;

  ret = fread ( site_data, header -> size_of_element,
		 nsites * header -> elements_per_site, fh -> data_fp );
  if ( ret != nsites * header -> size_of_element *
       header -> elements_per_site )
    status += 1;

  if ( fh -> swap_bytes )
    {
      int cnt;
      switch ( header -> size_of_element )
	{
	case 2: /* 16-bit: short */
	  for ( cnt = 0; cnt < nsites * header -> elements_per_site; ++cnt )
	    IO_swap16 ( static_cast ( IO_type16*, site_data ) + cnt );
	  break;

	case 4: /* 32-bit: long, float */
	  for ( cnt = 0; cnt < nsites * header -> elements_per_site; ++cnt )
	    IO_swap32 ( static_cast ( IO_type32*, site_data ) + cnt );
	  break;

	case 8: /* 64-bit: double */
	  for ( cnt = 0; cnt < nsites * header -> elements_per_site; ++cnt )
	    IO_swap64 ( static_cast ( IO_type64*, site_data ) + cnt );
	  break;

	default:
	  fprintf ( stderr, "unsupported data size_of_element: (%d)\n",
		    header -> size_of_element );
	  exit ( 1 );
	}
    }

  return status;
}

int IO_number_of_records ( IO_filehand* fh, IO_uni_header* header )
{
  struct stat buf;
  off_t data_bytes, bytes_per_record;
  long rem;
  int n_rec;

  if ( fstat ( fileno ( fh -> data_fp ), &buf ) )
    {
      fprintf ( stderr, "cannot fstat %s\n", fh -> filename );
      exit ( 1 );
    }

  data_bytes = buf.st_size - sizeof(IO_uni_header);
  bytes_per_record = (header -> size_of_element) *
    (header -> elements_per_site) * (header -> dim[0]) *
    (header -> dim[1]) * (header -> dim[2]) * (header -> dim[3]);
  n_rec = data_bytes / bytes_per_record;
  rem = data_bytes - n_rec * bytes_per_record;
  if ( rem != 0 )
    {
      fprintf ( stderr, "file contains an incomplete record of %ld bytes.\n",
		rem );
      exit ( 1 );
    }

  return ( n_rec );
}
